package top.cardone.data.jdbc.datasource.looku.impl;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.Transactional;
import top.cardone.context.ApplicationContextHolder;
import top.cardone.data.jdbc.datasource.looku.RoutingDataSource;
import top.cardone.data.jdbc.datasource.looku.RoutingDataSourceAspect;

import java.util.Map;

/**
 * @author yao hai tao
 */
@lombok.extern.log4j.Log4j2
public class RoutingDataSourceAspectImpl implements RoutingDataSourceAspect {
	@lombok.Setter
	private String defaultTransationManagerName = "transationManager";

	@lombok.Setter
	private Map<String, RoutingDataSource> routingDataSourceMap = com.google.common.collect.Maps.newConcurrentMap();

	private Transactional findTransactional(ProceedingJoinPoint joinPoint) {
		final java.lang.reflect.Method method = ((org.aspectj.lang.reflect.MethodSignature) joinPoint.getSignature()).getMethod();

		return org.springframework.core.annotation.AnnotationUtils.findAnnotation(method, org.springframework.transaction.annotation.Transactional.class);
	}

	private RoutingDataSource getRoutingDataSource(Transactional transactional) {
		if (transactional == null) {
			return null;
		}

		String transationManagerName = StringUtils.defaultString(transactional.value(), defaultTransationManagerName);

		RoutingDataSource routingDataSource = routingDataSourceMap.get(transationManagerName);

		if (routingDataSource != null) {
			return routingDataSource;
		}

		return ApplicationContextHolder.func(DataSourceTransactionManager.class, dataSourceTransactionManager -> {
			if (!(dataSourceTransactionManager.getDataSource() instanceof RoutingDataSource)) {
				return null;
			}

			RoutingDataSource dataSource = (RoutingDataSource) dataSourceTransactionManager.getDataSource();

			routingDataSourceMap.put(transationManagerName, dataSource);

			return dataSource;
		}, transationManagerName);
	}

	@Override
	public Object postProcessBeforeSetTargetDataSource(ProceedingJoinPoint joinPoint) throws Throwable {
		RoutingDataSource routingDataSource = null;

		try {
			Transactional transactional = this.findTransactional(joinPoint);

			routingDataSource = getRoutingDataSource(transactional);

			if (routingDataSource != null) {
				routingDataSource.addKey(transactional.readOnly());
			}
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}

		try {
			return joinPoint.proceed();
		} finally {
			if (routingDataSource != null) {
				routingDataSource.removeKey();
			}
		}
	}
}
